package com.bijava.support.impl;

import cn.org.atool.fluent.mybatis.base.IEntity;
import cn.org.atool.fluent.mybatis.base.crud.IQuery;
import cn.org.atool.fluent.mybatis.base.crud.IUpdate;
import cn.org.atool.fluent.mybatis.base.mapper.IWrapperMapper;
import cn.org.atool.fluent.mybatis.model.IPagedList;
import cn.org.atool.fluent.mybatis.model.StdPagedList;
import cn.org.atool.fluent.mybatis.segment.OrderByBase;
import cn.org.atool.fluent.mybatis.segment.WhereBase;
import cn.org.atool.fluent.mybatis.segment.where.StringWhere;
import com.alibaba.fastjson.JSON;
import com.bijava.annotation.DynamicSql;
import com.bijava.annotation.RelationType;
import com.bijava.model.PageQuery;
import com.bijava.support.BaseService;
import com.bijava.utils.BeanMapper;
import com.bijava.utils.CoreUtils;
import com.bijava.utils.LoggerHelper;
import ma.glasnost.orika.metadata.Type;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ReflectionUtils;

import java.io.Serializable;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * @author bishichu
 * @version 1.0
 * @since 20-11-13
 */
public abstract class AbstractBaseService<E extends IEntity, V, Q extends IQuery<E>, U extends IUpdate<E>,M extends IWrapperMapper<E, Q, U>> implements BaseService<E, V> {
    @Autowired(required = false)
    public M mapper;

    /**
     * 实体类的class
     */
    private Class<E> entityClass;

    /**
     * 实体类型，用于bean转换
     */
    private Type<E> entityType;

    /**
     * vo类的class
     */
    private Class<V> voClass;

    /**
     * vo类型，用于bean转换
     */
    private Type<V> voType;

    protected AbstractBaseService() {
        java.lang.reflect.Type[] types = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
        entityClass = (Class<E>) types[0];
        entityType = BeanMapper.getType(entityClass);
        voClass = (Class<V>) types[1];
        voType = BeanMapper.getType(voClass);
    }

    @Override
    public V selectByPrimaryKey(Object key) {
        return null;
    }

    @Override
    public <T> V selectOneByObject(T obj) {
        return null;
    }

    @Override
    public <T> int countByObject(T obj) {
        E entity = objToEntity(obj);
        Q nQuery = mapper.query();
        nQuery.where()
                .eqByEntity(entity);
        return mapper.count(nQuery);
    }

    @Override
    public <T> int countByObjectExclusiveOwn(T obj, String primaryKey, String primaryValue) {
        E entity = objToEntity(obj);
        mapper.query()
                .where().eqByEntity(entity);
        return 0;
    }

    @Override
    public <T> List<V> listByObject(T obj) {
        return null;
    }

    @Override
    public List<V> listAll() {
        return null;
    }

    @Override
    public <T> IPagedList<V> listByPage(PageQuery<T> pageQuery) {
        Q nQuery = this.obtainBaseQuery(pageQuery, false);
        StdPagedList<E> result = mapper.stdPagedEntity(nQuery);
        return entityToVo(result);
    }

    @Override
    public <T> IPagedList<V> listByPageWithDynamicSql(PageQuery<T> pageQuery) {
        Q nQuery = this.obtainBaseQuery(pageQuery, true);
        StdPagedList<E> result = mapper.stdPagedEntity(nQuery);

        return entityToVo(result);
    }

    @Override
    public <T> V insert(T obj) {
        E entity = objToEntity(obj);
        int result = mapper.insertWithPk(entity);
        if (result > 0) {
            return entityToVo(entity);
        } else {
            return null;
        }
    }

    @Override
    public <T> V insertNotNull(T obj) {
        E entity = objToEntity(obj);
        int result = mapper.insertWithPk(entity);
        if (result > 0) {
            return entityToVo(entity);
        } else {
            return null;
        }
    }

    @Override
    public <T> int insertBatch(List<T> objList) {
        List<E> eList = objToEntity(objList);
        return mapper.insertBatchWithPk(eList);
    }

    @Override
    public <T> int insertBatchUseGeneratedKeys(List<T> objList) {
        List<E> eList = objToEntity(objList);
        return mapper.insertBatch(eList);
    }

    @Override
    public <T> V updateById(T obj) {
        E e = objToEntity(obj);
        //preUpdate(e, CoreConstant.UPDATE_TYPE_ALL);
        int result = mapper.updateById(e);
        if (result > 0) {
            return entityToVo(e);
        } else {
            return null;
        }
    }

    @Override
    public <T> V updateNotNull(T obj) {
        E e = objToEntity(obj);
        //preUpdate(e, CoreConstant.UPDATE_TYPE_ALL);
        int result = mapper.updateById(e);
        if (result > 0) {
            return entityToVo(e);
        } else {
            return null;
        }
    }

    @Override
    public int deleteById(Serializable key) {
        return mapper.deleteById(key);
    }

    @Override
    public <T> int deleteByObject(T obj) {
        String toJson = JSON.toJSONString(obj);
        Map<String, Object> objectMap = JSON.parseObject(toJson, Map.class);
        return mapper.deleteByMap(false,objectMap);
    }

    @Override
    public <T> int deleteBatchByPK(List<T> objList) {
        List<E> eList = objToEntity(objList);
        return mapper.deleteByIds(eList);
    }

    /**
     * vo转entity
     *
     * @param vo
     * @return
     */
    protected E voToEntity(V vo) {
        return BeanMapper.map(vo, voType, entityType);
    }

    /**
     * vo转entity
     *
     * @param voList
     * @return
     */
    protected List<E> voToEntity(List<V> voList) {
        return BeanMapper.mapList(voList, voType, entityType);
    }

    /**
     * obj转entity
     *
     * @param obj
     * @param <T>
     * @return
     */
    protected <T> E objToEntity(T obj) {
        Type<T> objType = (Type<T>) BeanMapper.getType(obj.getClass());
        return BeanMapper.map(obj, objType, entityType);
    }

    /**
     * obj转entity
     *
     * @param objList
     * @param <T>
     * @return
     */
    protected <T> List<E> objToEntity(List<T> objList) {
        Type<T> objType = (Type<T>) BeanMapper.getType(objList.get(0).getClass());
        return BeanMapper.mapList(objList, objType, entityType);
    }

    /**
     * entity转vo
     *
     * @param entity
     * @return
     */
    protected V entityToVo(E entity) {
        return BeanMapper.map(entity, entityType, voType);
    }

    /**
     * entity转vo
     *
     * @param entityList
     * @return
     */
    protected List<V> entityToVo(List<E> entityList) {
        return BeanMapper.mapList(entityList, entityType, voType);
    }

    /**
     * entity转VO
     * @param pageList
     * @return
     */
    protected IPagedList<V> entityToVo(StdPagedList<E> pageList) {
        List<E> entityList = pageList.getData();
        List<V> voList = entityToVo(entityList);
        return new StdPagedList(pageList.getTotal(),voList,pageList.hasNext());
    }

    protected <T> Q obtainBaseQuery(PageQuery<T> pageQuery,boolean isDynamic) {
        Q nQuery = mapper.query();
        WhereBase where = nQuery.where();
        Class<? extends WhereBase> whereClass = where.getClass();
        nQuery.limit(pageQuery.getPageNo(), pageQuery.getPageSize());
        try {
            T condition = pageQuery.getCondition();
            Class<?> aClass = condition.getClass();
            List<Field> fieldList = new ArrayList<>();
            //获取所有属性包含父类
            while (aClass != null) {
                Field[] fields = aClass.getDeclaredFields();
                fieldList.addAll(Arrays.asList(fields));
                aClass = aClass.getSuperclass();
            }

            if (isDynamic) {
                for (Field field : fieldList) {
                    obtainDynamicSql(where, whereClass, condition, field);
                }
            } else {
                for (Field field : fieldList) {
                    obtainObjectSql(where, whereClass, condition, field);
                }
            }

            //设置排序
            assembleOrderBy(pageQuery, nQuery);
        } catch (IllegalAccessException | NoSuchFieldException e) {
            LoggerHelper.error("组装动态sql发生异常：{}",e.getMessage());
        }


        return nQuery;
    }

    private <T> void obtainDynamicSql(WhereBase where, Class<? extends WhereBase> whereClass, T condition, Field field) throws IllegalAccessException {
        DynamicSql annotation = field.getAnnotation(DynamicSql.class);
        if (annotation == null) {
            return;
        }

        ReflectionUtils.makeAccessible(field);
        Object value = field.get(condition);
        if (value != null && StringUtils.isNotBlank(value.toString())) {
            String fieldName = field.getName();
            RelationType relation = annotation.relation();
            judgeRelation(whereClass, where, relation, fieldName, value);
        }
    }

    private <T> void obtainObjectSql(WhereBase where, Class<? extends WhereBase> whereClass, T condition, Field field) throws IllegalAccessException {
        ReflectionUtils.makeAccessible(field);
        int modifiers = field.getModifiers();
        boolean isStatic = Modifier.isStatic(modifiers);
        if (!isStatic) {
            Object value = field.get(condition);
            if (value != null && StringUtils.isNotBlank(value.toString())) {
                String fieldName = field.getName();
                RelationType relation = RelationType.EQUAL;
                judgeRelation(whereClass, where, relation, fieldName, value);
            }
        }
    }

    protected <T> void assembleOrderBy(PageQuery<T> pageQuery, Q nQuery) throws NoSuchFieldException, IllegalAccessException {
        //设置排序
        Map<String, String> orderMap = pageQuery.getOrderMap();
        if (orderMap != null && !orderMap.isEmpty()) {
            Class<? extends IQuery> nQueryClass = nQuery.getClass();
            Field orderByField = nQueryClass.getField("orderBy");
            OrderByBase orderByBase = (OrderByBase) orderByField.get(nQuery);

            orderMap.forEach((key, value) -> {
                key = CoreUtils.camelToUnderline(key);
                if ("DESC".equals(value)) {
                    orderByBase.desc(key);
                } else {
                    orderByBase.asc(key);
                }
            });
        }
    }

    /**
     * 判断查询条件关系
     * @param where
     * @param relation
     * @param fieldName
     * @param value
     */
    private void judgeRelation(Class<? extends WhereBase> whereClass,WhereBase where, RelationType relation, String fieldName, Object value) {
        try {
            Method operationTypeMethod = whereClass.getMethod(fieldName);
            StringWhere fieldWhere = (StringWhere) operationTypeMethod.invoke(where);
            switch (relation) {
                case IN:
                    String[] inValues = value.toString().split(",");
                    fieldWhere.in(inValues);
                    break;
                case NOTIN:
                    String[] notinValues = value.toString().split(",");
                    fieldWhere.notIn(notinValues);
                    break;
                case EQUAL:
                    fieldWhere.eq(value);
                    break;
                case NOTEQUAL:
                    fieldWhere.ne(value);
                    break;
                case LIKE:
                    fieldWhere.like(value.toString());
                    break;
                case NOTLIKE:
                    fieldWhere.notLike(value.toString());
                    break;
                case GT:
                    fieldWhere.gt(value);
                    break;
                case GTE:
                    fieldWhere.ge(value);
                    break;
                case LT:
                    fieldWhere.lt(value);
                    break;
                case LTE:
                    fieldWhere.le(value);
                    break;
                default:
                    break;
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
